<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 * @author   howe <howe1995@163.com>
 */

namespace App\Services;

use App\Constant\ApiCodeEnum;
use App\Constant\Database\FileEnum;
use App\Constant\Database\FriendEnum;
use App\Constant\Database\MessageEnum;
use App\Constant\TalkEventConstant;
use App\Event\TalkEvent;
use App\Jobs\AssistantReplyJob;
use App\Model\File;
use App\Model\Friend;
use App\Model\Group;
use App\Model\GroupUser;
use App\Model\Message;
use App\Model\User;
use App\Repository\UserRepository;
use Exception;
use Hyperf\DbConnection\Db;

use function Hyperf\Support\env;

class MessageService extends BaseService
{
    /**
     * 聊天消息列表.
     */
    public function list(array $params): array
    {
        $fromUser = (int) auth()->user()->id;
        $toUser = (int) $params['to_user'];
        $isGroup = $params['is_group'];
        $me = [
            'id' => $fromUser,
            'nickname' => auth()->user()->nickname,
            'avatar' => auth()->user()->avatar,
            'wechat' => auth()->user()->wechat,
        ];
        // $query = Message::query();
        if ($isGroup) {
            $where = "(to_user = {$toUser} AND is_group=1)";
        // $query->where('is_group', 1)->where('to_user', $toUser);
        } else {
            $where = "((from_user = {$fromUser} AND to_user = {$toUser} AND is_group = 0) OR (from_user = {$toUser} AND to_user = {$fromUser} AND is_group = 0))";
            // $query->where(function ($q) use ($fromUser, $toUser) {
            //     $q->where(function ($subQ) use ($fromUser, $toUser) {
            //         $subQ->where('from_user', $fromUser)
            //             ->where('to_user', $toUser)
            //             ->where('is_group', 0);
            //     })
            //         ->orWhere(function ($subQ) use ($fromUser, $toUser) {
            //             $subQ->where('from_user', $toUser)
            //                 ->where('to_user', $fromUser)
            //                 ->where('is_group', 0);
            //         });
            // });
        }

        $list = [];
        // 聊天记录
        $messages = Message::query()
            ->whereRaw($where)
            ->whereRaw("(FIND_IN_SET('{$fromUser}', deleted_users) = '')")
            ->orderBy('created_at')
            ->get()
            ->toArray();
        if (! $messages) {
            return $list;
        }
        // 引用消息
        $parentIds = array_column($messages, 'pid');
        $parentIds = array_filter($parentIds);
        $parentMessages = [];
        if ($parentIds) {
            $parentMessages = Message::with(['from_user' => function ($query) {
                return $query->select(['id', 'nickname', 'avatar', 'wechat']);
            }])->whereIn('id', $parentIds)->get()->toArray();
            $parentMessages = array_column($parentMessages, null, 'id');
            $files = $this->getFiles($parentMessages);
            foreach ($parentMessages as $pk => $parentMessage) {
                $parentMessages[$pk] = $this->handleMessage($parentMessage, $toUser, 0, $files);
            }
        }

        $files = $this->getFiles($messages);

        foreach ($messages as $message) {
            $item = $this->handleMessage($message, $toUser, 0, $files);
            $atUsers = explode(',', $message['at_users']);
            $atUsers = array_filter($atUsers);
            $item['at_users'] = array_map('intval', $atUsers);
            ! empty($parentMessages[$message['pid']]) && $item['parent'] = $parentMessages[$message['pid']];
            $item['right'] = $message['from_user'] == $fromUser;
            $list[] = $item;
        }
        unset($message, $messages);

        if ($params['is_group'] == MessageEnum::GROUP) {
            // 群聊
            $userIds = array_column($list, 'from_user');
            $userIds = array_unique($userIds);
            $userList = User::query()->whereIn('id', $userIds)->get(['id', 'nickname', 'avatar', 'wechat'])->toArray();

            // 本群显示昵称 显示优先级最高
            $groupUserList = GroupUser::query()
                ->where('group_id', $toUser)
                ->whereIn('user_id', $userIds)
                ->get(['nickname', 'user_id'])->toArray();
            // 朋友昵称 显示优先级第二

            $friendList = Friend::query()
                ->where('owner', $fromUser)
                ->whereIn('friend', $userIds)
                ->where('status', FriendEnum::STATUS_PASS)
                ->get(['nickname', 'friend'])->toArray();

            $groupUserList = array_column($groupUserList, 'nickname', 'user_id');
            $friendList = array_column($friendList, 'nickname', 'friend');

            // 处理群成员昵称显示 默认用户昵称
            foreach ($userList as &$user) {
                if (! empty($friendList[$user['id']])) {
                    $user['nickname'] = $friendList[$user['id']];
                }
                if (! empty($groupUserList[$user['id']])) {
                    $user['nickname'] = $groupUserList[$user['id']];
                }
            }
            unset($user, $groupUserList, $friendList, $userIds);

            $userList = array_column($userList, null, 'id');

            foreach ($list as &$item) {
                // 撤回处理
                if ($item['is_undo']) {
                    $who = $item['from_user'] == $fromUser ? '你' : $userList[$fromUser]['nickname'];
                    $item['content'] = $who . '撤回了一条消息';
                }
                $item['from'] = $userList[$item['from_user']];
            }
            unset($item);
            // 标记已读
            GroupUser::query()->where('user_id', $fromUser)->where('group_id', $toUser)->update(['unread' => 0]);
        } else {
            // 私聊
            $user = User::query()->where('id', $toUser)->first(['id', 'nickname', 'avatar', 'wechat']);
            $friend = Friend::query()->where('owner', $fromUser)
                ->where('friend', $toUser)
                ->first(['id', 'nickname']);
            $user->nickname = $friend->nickname ?: $user->nickname;
            foreach ($list as &$item) {
                // 撤回处理
                if ($item['is_undo']) {
                    $who = $item['from_user'] == $fromUser ? '你' : $user->nickname;
                    $item['content'] = $who . '撤回了一条消息';
                }
                $item['from'] = $item['from_user'] == $fromUser ? $me : $user->toArray();
            }
            unset($item);
            // 标记已读
            Friend::query()->where('owner', $fromUser)->where('friend', $toUser)->update(['unread' => 0]);
        }
        return $list;
    }

    /**
     * 发送消息.
     */
    public function send(array $params): array
    {
        $isGroup = intval($params['is_group']);
        if (! in_array($params['type'], MessageEnum::TYPE)
            || ! in_array($isGroup, MessageEnum::IS_GROUP)) {
            $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
        }
        $fromUser = auth()->user()->id;
        $toUser = $params['to_user'];
        $time = time();
        $message = $this->handleMessage($params, $toUser, $fromUser, []);
        $message['time'] = $time;
        $assistantIds = get_assistant_ids();
        $atUserStr = $params['at_users'] ?? '';
        $atUsers = explode(',', $atUserStr);
        $atUsers = array_filter($atUsers);
        $sendData = [
            'who' => TalkEventConstant::WHO_MESSAGE,
            'action' => TalkEventConstant::ACTION_SEND,
            'data' => $message,
        ];

        $data = [
            'from_user' => $fromUser,
            'to_user' => $toUser,
            'content' => $params['content'],
            'is_group' => $isGroup,
            'type' => $params['type'],
            'pid' => $params['pid'] ?? 0,
            'at_users' => $atUserStr,
            'created_at' => $time,
        ];

        // 文件消息处理
        if (in_array($params['type'], FileEnum::TYPE)) {
            if (empty($params['file_id'])) {
                $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
            }
            $fileId = $params['file_id'];
            $file = File::query()->find($fileId);
            if ($file) {
                $data['file_id'] = $file->id;
                $data['file_name'] = $file->name;
                $data['file_type'] = $file->type;
                $data['file_size'] = $file->size;
                $sendData['data']['extends'] = [
                    'path' => str_replace(env('STATIC_FILE_URL'), '', $file->path),
                    'format' => $file->format,
                    'width' => $file->width,
                    'height' => $file->height,
                    'duration' => $file->duration,
                ];
                $sendData['data']['file'] = [
                    'id' => $fileId,
                    'name' => $file->name,
                    'type' => $file->type,
                    'size' => $file->size,
                ];
                //                $data['extends'] = json_encode([]);
                $sendData['data']['content'] = $file->path;
            }
        }

        // 消息内容简称处理
        if ($params['type'] !== MessageEnum::TEXT) {
            $data['content'] = MessageEnum::SIMPLE_CONTENT[$params['type']];
        }

        Db::beginTransaction();
        try {
            if ($isGroup == MessageEnum::GROUP) {
                $group = Group::query()->find($toUser);
                if ($atUsers) {
                    $atUsers = array_map('intval', $atUsers);
                    $originAtUsers = explode(',', $group->at_users);
                    $updateAtUsers = array_unique(array_merge($originAtUsers, $atUsers));
                    $group->at_users = implode(',', $updateAtUsers);
                }
                $group->send_user = $fromUser;
                $group->content = $data['content'];
                $group->time = $time;
                $group->save();
                GroupUser::query()
                    ->where('group_id', $toUser)
                    ->update([
                        'display' => 1,
                    ]);
                GroupUser::query()
                    ->where('group_id', $toUser)
                    ->where('user_id', '<>', $fromUser)
                    ->increment('unread');
            } else {
                if (empty($params['id'])) {
                    Friend::query()
                        ->whereRaw("((owner = {$fromUser} AND friend = {$toUser}) OR (friend = {$fromUser} AND owner = {$toUser}))")
                        ->update([
                            'display' => 1,
                            'content' => $data['content'],
                            'time' => $time,
                        ]);
                    Friend::query()
                        ->whereRaw("(friend = {$fromUser} AND owner = {$toUser})")
                        ->increment('unread');
                }
            }

            // 通话处理
            if (in_array($params['type'], [MessageEnum::VIDEO_CALL, MessageEnum::AUDIO_CALL])) {
                $data['content'] = $params['content'];
                $sendData['who'] = TalkEventConstant::WHO_USER;
                $sendData['action'] = TalkEventConstant::ACTION_CALL;
                ! empty($params['action']) && $sendData['data']['action'] = $params['action'];
                ! empty($params['offer']) && $sendData['data']['offer'] = $params['offer'];
                ! empty($params['answer']) && $sendData['data']['answer'] = $params['answer'];
                ! empty($params['candidate']) && $sendData['data']['candidate'] = $params['candidate'];
                if (empty($params['id'])) {
                    $data['deleted_users'] = "{$toUser}";
                } elseif ($params['id'] > 0 && empty($params['candidate'])) {
                    $sendData['data']['id'] = $params['id'];
                    $updateData = ['content' => $data['content']];
                    if (! empty($params['duration'])) {
                        $updateData['extends'] = json_encode(['duration' => $params['duration']]);
                    }
                    Message::query()->where('id', $params['id'])->update($updateData);
                }
            }

            if (empty($params['id'])) {
                $sendData['data']['id'] = Message::query()->insertGetId($data);
            }
            Db::commit();

            // at用户处理
            if ($atUsers) {
                $sendData['data']['at_users'] = $atUsers;
                $sendAtData = $sendData;
                $sendAtData['action'] = TalkEventConstant::ACTION_AT;
                // 通知被at的用户
                foreach ($atUsers as $atUser) {
                    $sendAtData['data']['to_user'] = $atUser;
                    // Gateway::sendToUid($atUser, json_encode($sendAtData, JSON_UNESCAPED_UNICODE));
                    event()->dispatch(new TalkEvent($sendAtData));
                }
            }

            // 引用消息处理
            if (! empty($params['pid'])) {
                $parentMessage = Message::with(['from' => function ($query) {
                    return $query->select(['id', 'nickname', 'avatar', 'wechat']);
                }])->find($params['pid']);
                $parentMessage = $parentMessage ? $parentMessage->toArray() : [];
                $files = $this->getFiles([$parentMessage]);
                $sendData['data']['parent'] = $this->handleMessage($parentMessage, $toUser, 0, $files);
                $sendQuoteData = [
                    'who' => TalkEventConstant::WHO_MESSAGE,
                    'action' => TalkEventConstant::ACTION_QUOTE,
                    'data' => [],
                ];
                // 通知被引用消息的用户
                // Gateway::sendToUid($parentMessage['from_user'], json_encode(
                //     $sendQuoteData,
                //     JSON_UNESCAPED_UNICODE
                // ));
                event()->dispatch(new TalkEvent($sendQuoteData));
            }

            // ai小助手处理
            $aiData = $sendData['data'];
            $aiIds = [];
            if ($isGroup == MessageEnum::PRIVATE && in_array($toUser, $assistantIds)) {
                // 私聊ai小助手
                $aiIds[] = $toUser;
            }

            if ($atAiIds = array_intersect($assistantIds, $atUsers)) {
                // 群聊@ai小助手
                $aiIds = array_merge($aiIds, $atAiIds);
            }

            if ($aiIds) {
                // ai小助手回复消息
                foreach ($aiIds as $aiId) {
                    $aiData['to_ai'] = $aiId;
                    dispatch(new AssistantReplyJob($aiData));
                }
            }

            // 向用户发送消息通知
            if ($isGroup == MessageEnum::GROUP) {
                // $excludeClientId = Gateway::getClientIdByUid($fromUser);
                // Gateway::sendToGroup($toUser, json_encode($sendData, JSON_UNESCAPED_UNICODE), $excludeClientId);
                event()->dispatch(new TalkEvent($sendData));
            } else {
                // Log::channel(TalkEventConstant::LOG_CHANNEL)->info($params);
                // Gateway::sendToUid($toUser, json_encode($sendData, JSON_UNESCAPED_UNICODE));
                logInfo('send_to_uid', $sendData);
                event()->dispatch(new TalkEvent($sendData));
            }
        } catch (Exception $e) {
            Db::rollBack();
            $this->throwBusinessException(ApiCodeEnum::SYSTEM_ERROR, $e->getMessage());
        }

        $sendData['data']['right'] = true;
        $sendData['data']['from']['nickname'] = auth()->user()->nickname;
        return $sendData['data'];
    }

    /**
     * 聊天消息已读.
     */
    public function read(array $params): int
    {
        $fromUser = auth()->user()->id;
        $toUser = $params['to_user'];
        $isGroup = $params['is_group'];
        if (! empty($params['id'])) {
            try {
                Db::beginTransaction();
                // 单条at/引用消息标记已读
                $field = $params['type'] ?? 'at_users';
                if (Message::query()->whereRaw("FIND_IN_SET({$fromUser}, {$field})")->count() > 0) {
                    // 所有at/引用消息标记已读
                    if ($isGroup == MessageEnum::GROUP) {
                        $group = Group::query()->findOrFail($toUser);
                        $origin = explode(',', $group->{$field});
                        $update = array_diff($origin, [$fromUser]);
                        $group->{$field} = implode(',', $update);
                        $group->save();
                    } else {
                        $friend = Friend::query()->where('owner', $fromUser)->where('friend', $toUser)->first();
                        $origin = explode(',', $friend->{$field});
                        $update = array_diff($origin, [$fromUser]);
                        $friend->{$field} = implode(',', $update);
                        $friend->save();
                    }
                }
                $message = Message::query()->findOrFail($params['id']);
                $origin = explode(',', $message->{$field});
                $update = array_diff($origin, [$fromUser]);
                $message->{$field} = implode(',', $update);
                $message->save();
                Db::commit();
                return 1;
            } catch (Exception $e) {
                Db::rollBack();
                $this->throwBusinessException(ApiCodeEnum::SYSTEM_ERROR, $e->getMessage());
            }
        }
        if ($isGroup == MessageEnum::GROUP) {
            return GroupUser::query()->where('user_id', $fromUser)->where('group_id', $toUser)->update(['unread' => 0]);
        }
        return Friend::query()->where('owner', $fromUser)->where('friend', $toUser)->update(['unread' => 0]);
    }

    /**
     * 聊天消息撤回.
     */
    public function undo(array $params): bool
    {
        $id = $params['id'];
        $message = Message::query()->find($id);
        if (! $message) {
            $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
        }
        $message->is_undo = 1;
        $message->is_tips = 1;
        $message->updated_at = time();
        $message->save();
        return true;
    }

    /**
     * 未读聊天消息.
     */
    public function unread(int $userId): array
    {
        $group = GroupUser::query()
            ->where('user_id', $userId)
            ->where('display', 1)
            ->where('unread', '>', 0)
            ->sum('unread');
        $private = Friend::query()
            ->where('owner', $userId)
            ->where('display', 1)
            ->where('unread', '>', 0)
            ->sum('unread');
        $unread = UserRepository::getUnreadById($userId);
        $apply = $unread['apply'];
        $moment = $unread['moment'];

        $from = [];
        if ($moment['num'] > 0) {
            $from = User::query()->find($moment['from'], ['id', 'nickname', 'avatar', 'wechat']);
        }

        return [
            'chat' => $group + $private,
            'apply' => $apply,
            'friend' => $apply,
            'moment' => [
                'num' => (int) $moment['num'],
                'from' => $from,
            ],
            'discover' => (int) $moment['num'],
        ];
    }

    /**
     * 通用获取消息里的关联文件方法.
     */
    private function getFiles(array $messages): array
    {
        $fileIds = array_column($messages, 'file_id');
        $fileIds = array_filter($fileIds);
        $files = [];
        if ($fileIds) {
            $files = File::query()->whereIn('id', $fileIds)->get()->toArray();
            $files = array_column($files, null, 'id');
        }
        return $files;
    }

    /**
     * 通用处理消息方法.
     */
    private function handleMessage(array $message, int $toUser, int $fromUser, array $files): array
    {
        $item = [
            'id' => $message['id'] ?? 0,
            'from' => $message['from'] ?? [],
            'from_user' => $message['from_user'] ?? $fromUser,
            'to_user' => $toUser,
            'content' => $message['content'],
            'type' => $message['type'],
            'is_undo' => $message['is_undo'] ?? 0,
            'is_tips' => $message['is_tips'] ?? 0,
            'time' => ! empty($message['created_at']) ? strtotime($message['created_at']) : 0,
            'file' => [],
            'extends' => [],
            'pid' => 0,
            'parent' => [],
            'at_users' => [],
            'is_group' => intval($message['is_group']),
            'right' => false,
        ];
        if (in_array($message['type'], FileEnum::TYPE)) {
            $fileId = $message['file_id'];
            $file = $files[$fileId] ?? [];
            if ($file) {
                $item['content'] = $file['path'];
                $item['extends'] = [
                    'thumbnail' => $file['thumbnail_path'] ?: '',
                    'format' => $file['format'],
                    'width' => $file['width'],
                    'height' => $file['height'],
                    'duration' => $file['duration'],
                ];
                $item['file'] = [
                    'id' => $fileId,
                    'name' => $file['name'],
                    'type' => $file['type'],
                    'size' => $file['size'],
                    'format' => $file['format'],
                ];
            }
        }
        return $item;
    }
}
